﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace SceneEngine
{
    public class BaseObjectRenderer
    {
        private Model model;
        private string modelAsset;

        private Matrix[] bones;

        private List<BaseObject> instances;

        private List<ModelMeshPart> opaqueParts;
        private List<ModelMeshPart> transparentParts;

        private Dictionary<ModelMeshPart, Matrix> meshTransforms;

        public string ModelAsset 
        {
            get { return modelAsset; }
            set { modelAsset = value; } 
        }

        public BaseObjectRenderer(Model model, string modelAsset) 
        {
            this.model = model;
            instances = new List<BaseObject>();

            this.bones = new Matrix[this.model.Bones.Count];
            this.model.CopyAbsoluteBoneTransformsTo(bones);

            opaqueParts = new List<ModelMeshPart>();
            transparentParts = new List<ModelMeshPart>();

            meshTransforms = new Dictionary<ModelMeshPart, Matrix>();

            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    if (part.Effect is AlphaTestEffect)
                    {
                        transparentParts.Add(part);
                    }
                    else
                    {
                        opaqueParts.Add(part);
                    }

                    IEffectLights effect = part.Effect as IEffectLights;

                    if (effect != null)
                    {
                        effect.DirectionalLight0.Enabled = true;
                        effect.DirectionalLight0.Direction = Vector3.Normalize(new Vector3(-1, -1, -1));
                        effect.DirectionalLight0.DiffuseColor = Color.White.ToVector3();

                        effect.AmbientLightColor = new Vector3(0.25f, 0.25f, 0.25f);

                        effect.LightingEnabled = true;
                    }

                    meshTransforms.Add(part, bones[mesh.ParentBone.Index]);
                }
            }
        }

        public void SubmitInstance(BaseObject instance)
        {
            instances.Add(instance);
        }

        public void DrawTransparent()
        {
            if (instances.Count < 1)
            {
                return;
            }

            GraphicsDevice device = EngineGlobals.GraphicsDevice;

            for (int partIndex = 0; partIndex < transparentParts.Count; partIndex++)
            {
                ModelMeshPart part = transparentParts[partIndex];

                if (part.NumVertices < 1)
                {
                    continue;
                }

                device.SetVertexBuffer(part.VertexBuffer, part.VertexOffset);
                device.Indices = part.IndexBuffer;

                AlphaTestEffect effect = part.Effect as AlphaTestEffect;

                if (effect == null)
                {
                    continue;
                }

                effect.View = EngineGlobals.Camera.View;
                effect.Projection = EngineGlobals.Camera.Projection;

                effect.AlphaFunction = CompareFunction.GreaterEqual;
                effect.ReferenceAlpha = 200;

                for (int i = 0; i < instances.Count; i++)
                {
                    effect.World = meshTransforms[part] * instances[i].World;

                    EffectTechnique technique = part.Effect.CurrentTechnique;
                    int passCount = technique.Passes.Count;

                    for (int passIndex = 0; passIndex < passCount; passIndex++)
                    {
                        technique.Passes[passIndex].Apply();

                        device.DrawIndexedPrimitives(PrimitiveType.TriangleList,
                                                     0,
                                                     0,
                                                     part.NumVertices,
                                                     part.StartIndex,
                                                     part.PrimitiveCount);
                    }
                }
            }
        }
        public void DrawOpaque()
        {
            if (instances.Count < 1)
            {
                return;
            }

            GraphicsDevice device = EngineGlobals.GraphicsDevice;

            for (int partIndex = 0; partIndex < opaqueParts.Count; partIndex++)
            {
                ModelMeshPart part = opaqueParts[partIndex];

                if (part.NumVertices < 1)
                {
                    continue;
                }

                device.SetVertexBuffer(part.VertexBuffer, part.VertexOffset);
                device.Indices = part.IndexBuffer;

                IEffectMatrices effect = part.Effect as IEffectMatrices;

                effect.View = EngineGlobals.Camera.View;
                effect.Projection = EngineGlobals.Camera.Projection;

                if (part.Effect is BasicEffect)
                {
                    (part.Effect as BasicEffect).VertexColorEnabled = false;
                }
                

                for (int i = 0; i < instances.Count; i++)
                {
                    effect.World = meshTransforms[part] * instances[i].World;

                    EffectTechnique technique = part.Effect.CurrentTechnique;
                    int passCount = technique.Passes.Count;

                    for (int passIndex = 0; passIndex < passCount; passIndex++)
                    {
                        technique.Passes[passIndex].Apply();

                        device.DrawIndexedPrimitives(PrimitiveType.TriangleList,
                                                     0,
                                                     0,
                                                     part.NumVertices,
                                                     part.StartIndex,
                                                     part.PrimitiveCount);
                    }
                }
            }
        }

        public void EndDraw()
        {
            instances.Clear();
        }
    }
}
